﻿using System.Diagnostics;
using DimensionsHelper.LanguageService.Symbols;
using DimensionsHelper.LanguageService.Syntax;

namespace DimensionsHelper.LanguageService.Analyzer;

/// <summary>
///     此类<see cref="Binder" />只绑定<see cref="SourceFileType.Dms" />类型的源文件代码。
/// </summary>
/// <param name="workFolder">工作文件夹</param>
public class DmsBinder(WorkFolder workFolder) : Binder(workFolder)
{

    private AnalysisTree? _currentTree;

    private AnalysisTree CurrentTree
    {
        get
        {
            Debug.Assert(_currentTree != null);
            return _currentTree;
        }
    }

    protected override AnalysisTree BindTreeCore(SyntaxTree syntaxTree)
    {
        _currentTree = WorkFolder.GetAnalysisTree(syntaxTree);
        // 优先绑定 InputDataSource
        foreach (var input in syntaxTree.GetInputDataSources())
        {
            DeclareInputDataSource(input);
        }

        foreach (var statement in syntaxTree.GetNodes())
        {
            // Event (...)
            // ...
            // End Event
            if (statement.Is<EventSection>(SyntaxKinds.EventSection, out var eventSection))
            {
                BindEvent(eventSection);
            }
            // OutputDataSource
            else if (statement.Is<OutputDataSourceSection>(SyntaxKinds.OutputDataSourceSection, out var output))
            {
                BindOutputDataSource(output);
            }
            // Logging
            else if (statement.Is<LoggingSection>(SyntaxKinds.LoggingSection, out var logging))
            {
                BindLogging(logging);
            }
        }
        
        return _currentTree;
    }
    

    private void DeclareDmsSection(SectionSymbol section)
    {
        if (!CurrentTree.Symbols.DeclareSymbol(section))
        {
            Raise(DiagnosticDescriptors.RepetitiveSection, section.Declaration.NameIdentifier, section.FullName);
        }
    }


    private void BindDmsProperty(SectionSymbol sectionSymbol, DmsProperty? dmsProperty,
        AnalysisDmsSection analysisDmsSection, (string, DmsPropertyType, string[]?)[] propertyInfos)
    {
        if (dmsProperty == null)
        {
            return;
        }
        
        var symbol = new DmsPropertySymbol(sectionSymbol, dmsProperty);
        var analysisNode = new AnalysisDmsProperty(dmsProperty, analysisDmsSection);
        analysisNode.NameIdentifier.Bind(symbol);
        analysisDmsSection.AddChild(analysisNode);
        sectionSymbol.Locals.DeclareSymbol(symbol);

        // Check Value
        (string propertyName, DmsPropertyType expectedType, string[]? expectedNames) = propertyInfos
            .FirstOrDefault(p => p.Item1.Equals(dmsProperty.Name, StringComparison.OrdinalIgnoreCase));

        if (string.IsNullOrEmpty(propertyName))
        {
            return;
        }

        // 解析属性值
        var value = ResolveExpressionValue(dmsProperty.Value);

        // 解析属性值失败
        if (value.Value == null)
        {
            return;
        }

        // 检查属性值类型
        if (!ValidateDmsPropertyValue(value, expectedType))
        {
            Raise(DiagnosticDescriptors.CannotConvertTypeImplicitly, dmsProperty.Value ?? (ITextNode)dmsProperty,
                value.GetType().Name, expectedType.ToString());
            return;
        }

        // 检查属性值是否在预定义的值范围内
        if (expectedNames is { Length: > 0 })
        {
            var enumName = value.Value.ToString();
            if (enumName == null || expectedNames.FirstOrDefault(e => e.Equals(enumName, StringComparison.OrdinalIgnoreCase)) == null)
            {
                Raise(DiagnosticDescriptors.UnexpectedToken, dmsProperty.Value ?? (ITextNode)dmsProperty,
                    string.Join(" 或 ", expectedNames));
                return;
            }
        }

        // 更新属性值
        symbol.NormalizedValue = value.Value;
        analysisNode.NormalizedValue = value.Value;
    }


    private record ResolvedValue(object? Value, bool IsIdentifier = false);


    private ResolvedValue ResolveExpressionValue(Expression? expression)
    {
        if (expression == null || expression.IsNode(SyntaxKinds.Null))
        {
            return new ResolvedValue(null);
        }
        
        // bool
        if (expression.Is<BooleanLiteral>(SyntaxKinds.Boolean, out var booleanLiteral))
        {
            return new ResolvedValue(booleanLiteral.Value);
        }
        
        // string
        if (expression.Is<StringLiteral>(SyntaxKinds.String, out var stringLiteral))
        {
            return new ResolvedValue(stringLiteral.Value);
        }
        
        // integer
        if (expression.Is<IntegerLiteral>(SyntaxKinds.Integer, out var integerLiteral))
        {
            return new ResolvedValue(integerLiteral.Value);
        }
        
        // double
        if (expression.Is<DoubleLiteral>(SyntaxKinds.Double, out var doubleLiteral))
        {
            return new ResolvedValue(doubleLiteral.Value);
        }

        // id
        if (expression.Is<Identifier>(SyntaxKinds.Identifier, out var identifier))
        {
            return new ResolvedValue(identifier.Value, true);
        }
        
        // binary expression
        if (expression.Is<BinaryExpression>(SyntaxKinds.BinaryExpression, out var binaryExpression))
        {
            var left = ResolveExpressionValue(binaryExpression.LeftOperand);
            var right = ResolveExpressionValue(binaryExpression.RightOperand);
            
            if (left.Value == null || right.Value == null)
            {
                return new ResolvedValue(null);
            }

            switch (left.Value)
            {
                // string
                case string lString when right is { Value: string rString, IsIdentifier: false }:
                    return new ResolvedValue($"{lString}{rString}");
                // double
                case double when right.Value is double:
                case int when right.Value is double:
                case double when right.Value is int:
                    return new ResolvedValue((double)left.Value + (double)right.Value);
                // int
                case int leftInteger when right.Value is int rightInteger:
                    return new ResolvedValue(leftInteger + rightInteger);
            }

            return new ResolvedValue(null);
        }
        
        Raise(DiagnosticDescriptors.CannotResolveExpressionAsLiteral, expression);
        return new ResolvedValue(null);
    }


    private enum DmsPropertyType
    {
        String,
        Integer,
        Double,
        Boolean,
        EnumName
    }


    private static bool ValidateDmsPropertyValue(ResolvedValue value, DmsPropertyType expectType)
    {
        return (expectType, value) switch
        {
            (DmsPropertyType.String, { Value: string, IsIdentifier: false }) or 
            (DmsPropertyType.Integer, { Value: int }) or 
            (DmsPropertyType.Double, { Value: double or int }) or 
            (DmsPropertyType.EnumName, { Value: string }) or 
            (DmsPropertyType.Boolean, { Value: bool or int }) => true,
            _ => false
        };
    }


    private static readonly (string, DmsPropertyType, string[]?)[] InputDataSourcePropertyInfos =
    [
        ("ConnectionString", DmsPropertyType.String, null),
        ("SelectQuery", DmsPropertyType.String, null),
        ("UpdateQuery", DmsPropertyType.String, null),
        ("UseInputAsOutput", DmsPropertyType.Boolean, null),
        ("JoinKey", DmsPropertyType.String, null),
        // JoinType = "Full" | "Inner" | "Left"
        ("JoinType", DmsPropertyType.EnumName, [ "Full", "Inner", "Left" ]),
        ("JoinKeySorted", DmsPropertyType.Boolean, null)
    ];


    private void DeclareInputDataSource(InputDataSourceSection dataSource)
    {
        // InputDataSource(Name) 其中的 Name 不可忽略
        if (string.IsNullOrEmpty(dataSource.Name))
        {
            return;
        }
        
        var symbol = new SectionSymbol(SymbolKinds.InputDataSourceSection, dataSource);
        DeclareDmsSection(symbol);
        var analysisNode = new AnalysisDmsSection(dataSource, null);
        analysisNode.NameIdentifier.Bind(symbol);
        
        // ConnectionString
        // 属性不允许忽略
        if (dataSource.ConnectionStringProperty == null)
        {
            Raise(DiagnosticDescriptors.DmsPropertyIsRequired, dataSource.NameIdentifier, "ConnectionString");
        }
        else
        {
            BindDmsProperty(symbol, dataSource.ConnectionStringProperty, analysisNode, InputDataSourcePropertyInfos);
        }
        
        // SelectQuery
        BindDmsProperty(symbol, dataSource.SelectQueryProperty, analysisNode, InputDataSourcePropertyInfos);
        // UpdateQuery
        BindDmsProperty(symbol, dataSource.UpdateQueryProperty, analysisNode, InputDataSourcePropertyInfos);
        // UseInputAsOutput
        BindDmsProperty(symbol, dataSource.UseInputAsOutputProperty, analysisNode, InputDataSourcePropertyInfos);
        // JoinKey
        BindDmsProperty(symbol, dataSource.JoinKeyProperty, analysisNode, InputDataSourcePropertyInfos);
        // JoinType
        BindDmsProperty(symbol, dataSource.JoinTypeProperty, analysisNode, InputDataSourcePropertyInfos);
        // JoinKeySorted
        BindDmsProperty(symbol, dataSource.JoinKeySortedProperty, analysisNode, InputDataSourcePropertyInfos);
    }


    // OutputDataSource(name[, "description"])
    //    [ConnectionString = "<connection_string> | UseInputAsOutput = True|False"]
    //    [MetaDataOutputName = "<metadata_location>"]
    //    [UpdateQuery = "<update_query>"]
    //    [TableOutputName = "<table_output_name>"]
    //    [VariableOrder = "SELECTORDER"]
    // End OutputDataSource
    private static readonly (string, DmsPropertyType, string[]?)[] OutputDataSourcePropertyInfos =
    [
        ("ConnectionString", DmsPropertyType.String, null),
        ("UseInputAsOutput", DmsPropertyType.Boolean, null),
        ("MetaDataOutputName", DmsPropertyType.String, null),
        ("UpdateQuery", DmsPropertyType.String, null),
        ("TableOutputName", DmsPropertyType.String, null),
        ("VariableOrder", DmsPropertyType.String, null)
    ];


    private void BindOutputDataSource(OutputDataSourceSection dataSource)
    {
        // OutputDataSource(Name) 其中的 Name 不可忽略
        if (string.IsNullOrEmpty(dataSource.Name))
        {
            return;
        }

        var symbol = new SectionSymbol(SymbolKinds.OutputDataSourceSection, dataSource);
        DeclareDmsSection(symbol);
        var analysisNode = new AnalysisDmsSection(dataSource, null);
        analysisNode.NameIdentifier.Bind(symbol);

        // ConnectionString 和 UseInputAsOutput 属性不允许同时忽略
        if (dataSource.ConnectionStringProperty == null && dataSource.UseInputAsOutputProperty == null)
        {
            Raise(DiagnosticDescriptors.DmsPropertyIsRequired, dataSource.NameIdentifier, 
                "ConnectionString 或 UseInputAsOutput");
        }

        // ConnectionString
        BindDmsProperty(symbol, dataSource.ConnectionStringProperty, analysisNode, OutputDataSourcePropertyInfos);
        // UseInputAsOutput
        BindDmsProperty(symbol, dataSource.UseInputAsOutputProperty, analysisNode, OutputDataSourcePropertyInfos);
        // MetaDataOutputName
        BindDmsProperty(symbol, dataSource.MetadataOutputNameProperty, analysisNode, OutputDataSourcePropertyInfos);
        // UpdateQuery
        BindDmsProperty(symbol, dataSource.UpdateQueryProperty, analysisNode, OutputDataSourcePropertyInfos);
        // TableOutputName
        BindDmsProperty(symbol, dataSource.TableOutputNameProperty, analysisNode, OutputDataSourcePropertyInfos);
        // VariableOrder
        BindDmsProperty(symbol, dataSource.VariableOrderProperty, analysisNode, OutputDataSourcePropertyInfos);
    }


    // Logging(name[, "description"])
    //    Group = "Group"
    //    Path = "Path"
    //    Alias = "Alias"
    //    [FileSize = FileSize]
    // End
    private static readonly (string, DmsPropertyType, string[]?)[] LoggingPropertyInfos =
    [
        ("Group", DmsPropertyType.String, null),
        ("Path", DmsPropertyType.String, null),
        ("Alias", DmsPropertyType.String, null),
        ("FileSize", DmsPropertyType.Double, null)
    ];


    private void BindLogging(LoggingSection loggingSection)
    {
        var symbol = new SectionSymbol(SymbolKinds.LoggingSection, loggingSection);
        DeclareDmsSection(symbol);
        var analysisNode = new AnalysisDmsSection(loggingSection, null);
        analysisNode.NameIdentifier.Bind(symbol);
        // 检查必须的属性
        var missingProperties = new List<string>();
        if (loggingSection.GroupProperty == null)
        {
            missingProperties.Add("Group");
        }

        if (loggingSection.PathProperty == null)
        {
            missingProperties.Add("Path");
        }

        if (loggingSection.AliasProperty == null)
        {
            missingProperties.Add("Alias");
        }

        if (missingProperties.Count > 0)
        {
            Raise(DiagnosticDescriptors.DmsPropertyIsRequired, loggingSection.NameIdentifier,
                string.Join(", ", missingProperties));
        }

        // Group
        BindDmsProperty(symbol, loggingSection.GroupProperty, analysisNode, LoggingPropertyInfos);
        // Path
        BindDmsProperty(symbol, loggingSection.PathProperty, analysisNode, LoggingPropertyInfos);
        // Alias
        BindDmsProperty(symbol, loggingSection.AliasProperty, analysisNode, LoggingPropertyInfos);
        // FileSize
        BindDmsProperty(symbol, loggingSection.FileSizeProperty, analysisNode, LoggingPropertyInfos);
    }


    private void BindEvent(EventSection eventSection)
    {
        var eventSymbol = new SectionSymbol(SymbolKinds.EventSection, eventSection);
        var eventNode = new AnalysisEventSection(eventSection, eventSymbol, CurrentTree);
        // 提前声明函数
        var symbolTable = eventNode.GetDomainSymbolTable();
        // 对于 OnNextCase Event，引入 DOM 模块
        if (eventSection.NameIdentifier.Value.Equals("OnNextCase", StringComparison.OrdinalIgnoreCase) &&
            WorkFolder.GetWorkFolderSymbolTable().LookupDeclaredSymbol("DOM") is ModuleSymbol domModule)
        {
            symbolTable.ImportModule(domModule);
        }
        // 绑定语句体
        eventNode.UpdateBody(MrsBinder.BindBlockNodes(WorkFolder, eventSection.Body, symbolTable));
        CurrentTree.AddChild(eventNode);
    }
    
    
}
